from __future__ import annotations
import concurrent.futures
from enum import Enum
import importlib
import io
import json
import logging
from abc import ABC,abstractmethod

import concurrent
import multiprocessing
import time
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from dingtalk_service.yida_client_base import YidaClientBase
from reportservice.dbhelper import DBTool
from reportservice.helpers import ApiResponse
from django.db import connections, transaction
import threading
import datetime


from yida_app_service.utils import common_utils
from yida_app_service.utils.common_utils import HTTPUtils, SafeDict, SyncHandleRedisLock
from yida_app_service.utils.custom_db_utils import CustomDBTool
from yida_app_service.xu_cloud_client import XmCloudBaseClient

logger = logging.getLogger(__name__)

def get_yida_client(company_name):
    """
    根据提供的模块名称动态导入YidaClient类。

    :param company_name: 模块名称，比如 'zhongcheng' 或 'shujian'
    :return: 导入的YidaClient类
    """
    module_name = f'yida_app_service.{company_name}.yida_config'
    module = importlib.import_module(module_name)
    return getattr(module, 'YidaClient')

def get_xm_cloud_client(company_name):
    module_name = f'yida_app_service.{company_name}.xm_cloud_config'
    module = importlib.import_module(module_name)
    return getattr(module,'XmCloudClient')

@csrf_exempt
@require_http_methods(["GET"])
def get_jy_project_list_sqlsever_2008(request, company_name):
    is_async = int(request.GET.get('is_async', -1))
    page_size = int(request.GET.get('pageSize', 10))
    page = int(request.GET.get('currentPage', 1))
    ProjectNum = request.GET.get('ProjectNum', '')
    ProjectName = request.GET.get('ProjectName', '')

    # 计算 OFFSET 值
    offset = (page - 1) * page_size

    # 动态构建 is_async 条件
    if is_async == -1:
        async_param = "(is_async IS NULL OR is_async = 1 OR is_async = 0)"
    elif is_async == 0:
        async_param = "(is_async IS NULL OR is_async = 0)"
    elif is_async == 1:
        async_param = "is_async = 1"

    # 构建查询语句，包括分页和模糊查询，使用 ROW_NUMBER() 实现分页
    query = f"""
        SELECT * FROM (
            SELECT 
                ROW_NUMBER() OVER (ORDER BY LTRIM(projectNum)) AS RowNum,
                projectNum,
                projectName,
                entrustUnit,
                constractUnit,
                witnessUnit,
                constractionUnit,
                sendSampleMan,
                sendSampleManTel,
                witnessMan,
                is_async
            FROM 
                sysProjectInfomationLib
            WHERE 
                projectNum LIKE %s AND 
                projectName LIKE %s AND 
                {async_param}
        ) AS RowConstrainedResult
        WHERE 
            RowNum > %s AND 
            RowNum <= %s
        ORDER BY 
            RowNum
    """
    
    # 构建用于获取总记录数的查询语句
    query_total = f"""
        SELECT 
            COUNT(*)
        FROM 
            sysProjectInfomationLib
        WHERE 
            projectNum LIKE %s AND 
            projectName LIKE %s AND 
            {async_param}
    """
    
    # 分页参数计算
    start_row = offset
    end_row = offset + page_size
    
    params = (f"%{ProjectNum}%", f"%{ProjectName}%", start_row, end_row)
    params_total = (f"%{ProjectNum}%", f"%{ProjectName}%")
    
    # 执行查询
    db = DBTool(database=f"{company_name}_db")
    rows = db.execute_query(query, params)
    total = db.execute_query(query_total, params_total)[0][0]  # 总数将是第一个查询返回的第一个字段
    
    projects = []
    for row in rows:
        projects.append({
            "projectNum": row[1],
            "projectName": row[2],
            "entrustUnit": row[3],
            "constractUnit": row[4],
            "witnessUnit": row[5],
            "constractionUnit": row[6],
            "sendSampleMan": row[7],
            "sendSampleManTel": row[8],
            "witnessMan": row[9],
            "is_async":row[10]
        })

    return ApiResponse.success({
        'data': projects,
        'totalCount': total,
        "currentPage": page
    })

@csrf_exempt
@require_http_methods(["POST"])
@transaction.atomic
def async_jy_project_list_sqlsever_2008(request, company_name):
    params = json.loads(request.body)
    form_uuid = params.get("formUuid", "")
    admin_user_id = params.get("adminUserId", "")
    project_list:list[dict] = params.get("projectList", [])
    
    if form_uuid and admin_user_id and project_list:
        YidaClient = get_yida_client(company_name)
        yida_client:YidaClientBase = YidaClient()
        db = DBTool(database=f"{company_name}_db")
        
        project_nums = []
        for project in project_list:
            yida_client.create_form_data(form_uuid, admin_user_id, {
                "radioField_lw60e641": "立项项目",
                "textField_m0f2jc6b": project.get("projectNum", ""),
                "textField_m0f2jc6c": "同步",
                "textField_lvhea611": project.get("projectName"),
                "textField_lvhea612": project.get("entrustUnit"),
                "textField_lwylsxhz": project.get("constractUnit"),
                "textField_lvhea62s": project.get("witnessUnit"),
                "textField_lvhea628": project.get("constractionUnit"),
                "textField_lvhea620": project.get("sendSampleMan"),
                "textField_lwzspie1": project.get("sendSampleManTel"),
                "textField_lvhea62z": project.get("witnessMan"),
                "textField_lzw538la":"/"
            })
            project_nums.append(project.get("projectNum"))

        # 构建update_query
        if project_nums:
            project_nums_placeholder = ', '.join(f"'{num}'" for num in project_nums)
            update_query = f"""
                UPDATE sysProjectInfomationLib
                SET is_async = 1
                WHERE projectNum IN ({project_nums_placeholder})
            """
            db.execute_update(update_query)
            return ApiResponse.success("工程同步成功")
        
    return ApiResponse.fail("传参错误")

'''同步建研数据库'''
# 记录哪些记录已经被线程处理在同步中了，用于同步处理压缩
safe_sync_dict = SafeDict()
# @@!可以增加记录哪些记录已经被同步过了，但是用户可能会随时删除，这时候不好控制了，暂不考虑

'''同步工程表 sysProjectInfomationLib'''

def handle_sync_project(company_name,dml_pattern,projectNum):
    logger.info("工程同步sqlserver数据库到宜搭数据中心")
    # 创建宜搭客户端
    YidaClient = get_yida_client(company_name)
    yida_client:YidaClientBase = YidaClient()
    '''
    DML方式转换 !这里需要去宜搭表单检查是否有对应的数据记录，!!!如果有或者没有，可能需要转换下dml_pattern
    e.g. insert有的话无需插入了 delete没有的话无需再删除了 update有的话只需要更新，没有的话转为insert了
    '''
    # 获取主键
    project_num = projectNum
    # 获取表单相关信息
    form_uuid = yida_client.project_form_uuid
    admin_user_id = yida_client.admin_user_id
    project_num_component_id = yida_client.project_num_component_id
    # 构建同步处理器链
    # 创建云平台配置
    xm_cloud_client = get_xm_cloud_client(company_name=company_name)
    sync_handler_chain = SyncHandlerChain(yida_client=yida_client,xm_cloud_client=xm_cloud_client)    
    try:
        # 判断宜搭表中是否有对应的project_num记录
        result = yida_client.get_form_data_by_search_field(form_uuid=form_uuid,
                                                    userId=admin_user_id,
                                                    search_field=[
                                                        {
                                                            "type": "TEXT",
                                                            "operator": "eq",
                                                            "componentName": "TextField",
                                                            "key": project_num_component_id,
                                                            "value": project_num
                                                        }
                                                    ])
        has_data = bool(result)
        # 状态转换器转换状态
        # !现情况下只有insert和update两种状态
        transformer = DMLPatternTransformer(dml_pattern=dml_pattern)
        transformer.transform(has_data=has_data)
        # 设置全新的状态
        old_pattern = dml_pattern
        dml_pattern = transformer.get_status()
        logger.info(f"{'有数据' if has_data else '没有数据'} 转换后 {old_pattern + ' ——> ' + dml_pattern}")
        # 创建db_tool
        db_name = f"{company_name}_db"
        if yida_client.is_custom:
            db_tool = CustomDBTool(db_name)
        else:
            db_tool = DBTool(db_name)
        # 查询数据库数据
        anti_sql_injection = db_tool.anti_sql_injection
        query_sql = f"select * from sysProjectInfomationLib where projectNum = {anti_sql_injection}"
        # 删除同步项目编号的缓存
        # !插入或者更新前先删除，防止更新后没有释放key锁导致后面的更新被丢弃了，导致数据不一致
        safe_sync_dict.delete(projectNum)        
        db_data = db_tool.execute_query(query_sql,[project_num],return_dict=True)
        # 如果没有数据，返回False即可
        if not db_data:
            logger.warning(f"建研数据库工程同步表中不存在对应projectNum为{projectNum}的记录")
            return False
        # 有数据只有第一条
        db_data = db_data[0]
        match dml_pattern:
            case "insert":
                mapped_sync_data = sync_handler_chain.sync_data(db_data,yida_client.project_sync_mapping)
                is_success = yida_client.create_form_data(form_uuid=yida_client.project_form_uuid,userId=yida_client.admin_user_id,formDataJson=mapped_sync_data)
                rep_msg = "添加成功" if is_success else "添加失败"
            case "update":
                # 获取匹配的表单记录的id
                form_instance_id = result[0].form_instance_id
                # 转换映射表单同步数据
                mapped_sync_data =  sync_handler_chain.sync_data(db_data,yida_client.project_sync_mapping)
                is_success = yida_client.update_form_data_by_id(form_uuid=form_uuid,userId=admin_user_id,form_inst_id=form_instance_id,update_field=mapped_sync_data)
                rep_msg = "更新成功" if is_success else "更新失败"
    except Exception as error:
        # 删除同步项目编号的缓存
        safe_sync_dict.delete(projectNum)
        SyncErrorHandler.write_sync_error_data(sync_name="工程",error_data=f"projectNum={project_num};dml_pattern={dml_pattern}",error_info=error)
        return False    
    if is_success:
        logger.info(rep_msg)
        return True
    # 如果同步失败了
    else:
        warning_msg = "未知原因，"+rep_msg
        logger.warning(warning_msg)
        SyncErrorHandler.write_sync_error_data(sync_name="工程",error_data=f"projectNum={project_num};dml_pattern={dml_pattern}",error_info=warning_msg)
        return False

sync_project_executor_pool_max_workers = 4
sync_project_executor_pool = concurrent.futures.ThreadPoolExecutor(max_workers=sync_project_executor_pool_max_workers) # 同步工程线程池
sync_project_redis_lock_expire = 12 # redis锁的有效时间
sync_project_renew_sleep_count = 4 # 续签睡眠次数，次数耗尽进行续签
sync_project_get_redis_lock_period = sync_project_redis_lock_expire // 2 # 取过期时间的一半，获取redis锁的周期
sync_project_get_redis_lock_count = 10 # 获取redis锁最大次数，超过该次数则该次同步失败 !不能一直占有线程池的线程

def sync_project(company_name,dml_pattern,projectNum):
    # 尝试获取redis分布式锁
    lock_name = f"sync-yida:projectNum:{projectNum}"
    sync_project_redis_lock = SyncHandleRedisLock(lock_name=lock_name,expire=sync_project_redis_lock_expire,renew_sleep_count=sync_project_renew_sleep_count)
    # 尝试周期性获取锁
    if sync_project_redis_lock.acquire_loop(get_period=sync_project_get_redis_lock_period,get_count=sync_project_renew_sleep_count):
        handle_sync_project(company_name,dml_pattern,projectNum=projectNum)
        # 释放锁后返回即可
        sync_project_redis_lock.release()
        return
    # 多次获取redis-lock失败，结束任务
    # 删除同步项目编号的缓存
    safe_sync_dict.delete(projectNum)
    # 并记录同步失败的项目编号和原因
    SyncErrorHandler.write_sync_error_data(sync_name="工程",error_data=f"projectNum={projectNum};dml_pattern={dml_pattern}",error_info="多次获取redis锁后失败")


@csrf_exempt
@require_http_methods(["GET"])
def sync_project_jy_sqlserver_to_yida(request,company_name):
    '''
    同步sqlserver数据库到宜搭数据中心
    '''
    # 获取表单数据改为x-www-form
    req_data = request.GET
    # 获取操作方式 !删除不需要了
    allowed_patterns = ["insert","update"]
    dml_pattern:str = req_data.get("dml_pattern")
    logger.info(f"The dml pattern is {dml_pattern}")
    # 校验操作方式是否有效
    if not dml_pattern:
        return ApiResponse.fail(message="无效的DML操作方式")
    dml_pattern = dml_pattern.lower()
    if dml_pattern not in allowed_patterns:
        return ApiResponse.fail(message="无效的DML操作方式")
    projectNum = req_data.get("projectNum","")
    # 如果没有数据或者为空
    if not projectNum:
        return ApiResponse.success(message="同步的数据为空")
    # 检查本地缓存有无相应的项目编号存在
    # 不存在则说明目前还没有正在同步的项目
    if safe_sync_dict.get(projectNum) is None:
        # 加入该项目编号缓存
        set_success = safe_sync_dict.set(projectNum,1)
        # 只有设置成功才能提交任务
        if set_success:
            # 提交任务
            sync_project_executor_pool.submit(sync_project,company_name,dml_pattern,projectNum)
    logger.info(f"存在相同的项目编号:{projectNum}")
    return ApiResponse.success(message="同步项目请求发送成功")

@csrf_exempt
@require_http_methods(["POST"])
def sync_project_yida_to_jy_sqlserver(request,company_name):
    '''
    反向同步到数据库，需要整体事务的支持
    这里只需要考更新
    '''
    logger.info("工程反向同步宜搭数据中心到sqlserver数据库")
    req_data = request.POST
    # 获取操作方式
    allowed_patterns = ["update"]
    dml_pattern = req_data.get("dml_pattern")
    logger.info(f"The dml pattern is {dml_pattern}")
    # 校验操作方式是否有效
    if not dml_pattern:
        return ApiResponse.fail(message="无效的DML操作方式")
    dml_pattern = dml_pattern.lower()
    if dml_pattern not in allowed_patterns:
        return ApiResponse.fail(message="无效的DML操作方式")
    # 获取表单实例id
    form_instance_id = req_data.get("form_instance_id",{})
    # 如果没有数据或者为空
    if not form_instance_id:
        return ApiResponse.success(message="同步的数据为空")
    logger.info(f'反向同步实例id为 {form_instance_id}')
    # 创建宜搭客户端
    YidaClient = get_yida_client(company_name)
    yida_client:YidaClientBase = YidaClient()
    # 获取表单相关信息
    form_uuid = yida_client.project_form_uuid
    admin_user_id = yida_client.admin_user_id
    # 获取宜搭的表单某条记录
    # ?!删除的话表记录没了如何查询数据 @只管更新，不管删除
    table_items = yida_client.get_form_items_by_ids(form_uuid=form_uuid,form_inst_id=form_instance_id,userId=admin_user_id)
    if not table_items:
        return ApiResponse.fail(message="同步失败，未找到宜搭对应的表的实例id的记录信息")
    table_item = table_items[0]
    projectNum = table_item.form_data[yida_client.project_num_component_id]
    # 创建数据库工具
    db_name = f"{company_name}_db"
    if yida_client.is_custom:
        db_tool = CustomDBTool(db_name)
    else:
        db_tool = DBTool(db_name)
    form_data = table_item.form_data
    # 创建同步处理器链
    xm_cloud_client = get_xm_cloud_client(company_name=company_name)
    sync_handler_chain = SyncHandlerChain(yida_client=yida_client,xm_cloud_client=xm_cloud_client)
    updated_project_mapping = sync_handler_chain.inverse_sync_data(form_data,yida_client.project_sync_mapping,yida_client,association_config=None)
    # 构造update sql语句进行更新
    update_sql = "update sysProjectInfomationLib set "
    for key,value in updated_project_mapping.items():
        key_str:str = key
        if key_str.lower() == "projectnum":
            continue
        # 日期类型需要转化为 CONVERT(DATETIME, '2023-09-30', 120) 这里还是用key的后缀判断
        if key.endswith('Date') or key.endswith("date"):
            handled_value = value
        else:
            handled_value = f"'{value}'" # 注意需要用''包裹，因为值大多为字符串
        update_sql += f"{key} = {handled_value},"
    # 去除最后的 , 号
    update_sql = update_sql[0:len(update_sql)-1]
    # 添加更新条件
    # !!!这里不采用防止sql注入了，否则返回的更新行数为0太奇怪了
    update_sql += f" where projectNum = '{projectNum}'"
    logger.info(f"Execute update sql {update_sql}")
    affected_row = db_tool.execute_update(update_sql,[])
    match affected_row:
        case -1:
            return ApiResponse.fail(message="更新失败，请联系开发人员了解详情")
        case 0:
            return ApiResponse.success(message="更新成功，但数据库中不存在对应的记录")
        case _:
            return ApiResponse.success(message="更新成功")

'''同步样品信息到宜搭 @！一个委托有多个样品信息，要都同步，一个样品组有一个对应的检测项目规则'''
''' db_data 委托的一条记录，里面包含样品信息 !一定是在委托信息同步成功后再去同步样品信息'''
'''return 同步是否成功'''
def sync_sample_info(db_data:dict,yida_client:YidaClientBase,xm_cloud_client:XmCloudBaseClient) -> bool:
    # 1.获取样品编号并判断是否有效
    sample_num = db_data.get("SampleNum")
    # 如果是无效的样品编号
    if not sample_num:
        invalid_msg = f"无效的样品编号值{sample_num}"
        logger.warning(invalid_msg)
        return False
    # 2.从宜搭中判断该样品编号对应的记录是否存在
    sample_form_uuid = yida_client.sample_form_uuid
    admin_user_id = yida_client.admin_user_id
    # 从宜搭中查询
    try:
        sample_reuslt = yida_client.get_form_data_by_search_field(form_uuid=sample_form_uuid,
                                                                userId=admin_user_id,
                                                                search_field=[
                                                                    {
                                                                        "type":"TEXT",
                                                                        "operator":"eq",
                                                                        "componentName":"TextField",
                                                                        "key":"textField_m0g5ssx7",
                                                                        "value":sample_num
                                                                    }
                                                                ])
        # 创建同步处理器链
        sync_handler_chain = SyncHandlerChain(yida_client=yida_client,xm_cloud_client=xm_cloud_client)
        # 注册自定义的检测参数同步处理器
        sync_handler_chain.register_custom_handler(SubItemListTableSyncHandler)
        mapped_sync_data = sync_handler_chain.sync_data(db_data,yida_client.sample_sync_mapping)
        # 没有数据则插入
        if not sample_reuslt:
            dml_pattern = 'insert'
            logger.debug(f"宜搭不存在对应的样品记录信息记录，开始插入记录！")
            is_success = yida_client.create_form_data(form_uuid=sample_form_uuid,userId=admin_user_id,formDataJson=mapped_sync_data)
            result_msg = '添加成功' if is_success else '添加失败'
        # 有数据则更新
        else:
            dml_pattern = 'update'
            logger.debug(f"宜搭存在对应的样品信息记录，开始更新记录")
            sample_reuslt = sample_reuslt[0]
            is_success = yida_client.update_form_data_by_id(form_uuid=sample_form_uuid,userId=admin_user_id,form_inst_id=sample_reuslt.form_instance_id,update_field=mapped_sync_data)
            result_msg = '更新成功' if is_success else '更新失败'
    except Exception as error:
        SyncErrorHandler.write_sync_error_data(sync_name="样品",error_data=f"sampleNum={sample_num};dml_pattern={dml_pattern}",error_info=error)
        return False
    logger.info(f"同步样品信息结果:{result_msg}")
    # 如果处理失败需要额外记录
    if not is_success:
        SyncErrorHandler.write_sync_error_data(sync_name="样品",error_data=f"sampleNum={sample_num};dml_pattern={dml_pattern}",error_info="未知原因，同步失败！")
    return is_success

'''同步委托表 sysCurrentRecord'''

'''返回值 tuple 第一个值表示委托基本信息是否同步成功。第二个表示样品组信息是否同步成功，为None表示没有进行任何同步，False表示同步成功了一部分 True表示全部同步成功'''
def handle_sync_current_record(company_name,dml_pattern,entrustNum) -> tuple:
    logger.info("委托同步sqlserver数据库到宜搭数据中心")
    # 创建宜搭客户端
    YidaClient = get_yida_client(company_name)
    yida_client:YidaClientBase = YidaClient()   
    # 获取表单相关信息
    form_uuid = yida_client.current_record_form_uuid
    admin_user_id = yida_client.admin_user_id
    current_record_primary_key_component_id = yida_client.current_record_primary_key_component_id
    db_data = None
    # 创建云平台客户端
    xm_cloud_client = get_xm_cloud_client(company_name=company_name)
    # 创建同步处理器链
    sync_handler_chain = SyncHandlerChain(yida_client=yida_client,xm_cloud_client=xm_cloud_client)      
    try:
        # 判断宜搭表中是否有对应的entrustNum记录
        result = yida_client.get_form_data_by_search_field(form_uuid=form_uuid,
                                                    userId=admin_user_id,
                                                    search_field=[
                                                        {
                                                            "type": "TEXT",
                                                            "operator": "eq",
                                                            "componentName": "TextField",
                                                            "key": current_record_primary_key_component_id,
                                                            "value": entrustNum
                                                        }
                                                    ])
        has_data = bool(result)
        # 状态转换器转换状态
        transformer = DMLPatternTransformer(dml_pattern=dml_pattern)
        transformer.transform(has_data=has_data)
        # 设置全新的状态
        old_pattern = dml_pattern
        dml_pattern = transformer.get_status()
        logger.info(f"{'有数据' if has_data else '没有数据'} 转换后 {old_pattern + ' ——> ' + dml_pattern}")
        # 创建db_tool
        db_name = f"{company_name}_db"
        if yida_client.is_custom:
            db_tool = CustomDBTool(db_name)
        else:
            db_tool = DBTool(db_name)
        anti_sql_injection = db_tool.anti_sql_injection
        query_sql = f"select * from sysCurrentRecord where entrustNum = {anti_sql_injection}"
        # 删除本地委托编号缓存
        safe_sync_dict.delete(entrustNum)             
        db_data = db_tool.execute_query(query_sql,[entrustNum],return_dict=True)
        if not db_data:
            logger.warning(f"建研数据库委托同步表中不存在对应entrustNum为{entrustNum}的记录")
            return (False,None)  
        # 获取数据库记录，逐个遍历同步
        current_record_db_data = db_data[0]
        match dml_pattern:
            case "insert":
                mapped_sync_data = sync_handler_chain.sync_data(current_record_db_data,yida_client.current_record_sync_mapping)             
                is_success = yida_client.create_form_data(form_uuid=form_uuid,userId=admin_user_id,formDataJson=mapped_sync_data)
                logger.info("同步添加委托基本信息成功！")
            case "update":
                # 获取匹配的表单记录的id
                form_instance_id = result[0].form_instance_id
                mapped_sync_data = sync_handler_chain.sync_data(current_record_db_data,yida_client.current_record_sync_mapping)              
                is_success = yida_client.update_form_data_by_id(form_uuid=form_uuid,userId=admin_user_id,form_inst_id=form_instance_id,update_field=mapped_sync_data)
                logger.info("同步更新委托基本信息成功！")
    except Exception as error:
        # 删除本地委托编号缓存
        safe_sync_dict.delete(entrustNum)        
        entrustNum_error_log = f"entrustNum={entrustNum};dml_pattern={dml_pattern}"
        sample_error_log = None
        # 记录同步失败的样品信息 !!!样品组也要记录，如果有的话
        if db_data:
            sample_nums = []
            for sample_db_data in db_data:
                sample_nums.append(sample_db_data["SampleNum"])
            sample_nums_log = ','.join(sample_nums)
            sample_error_log = f"sampleNum={sample_nums_log}"
        else:
            logger.error(f"无法记录委托编号为{entrustNum}对应的样品组信息数据，查询数据库的数据无效！")
        # 如果有样品组数据就和委托日志合并
        if sample_error_log:
            entrustNum_error_log = entrustNum_error_log + ";" + sample_error_log
        SyncErrorHandler.write_sync_error_data(sync_name="委托",error_data=entrustNum_error_log,error_info=error)        
        return False
    # 同步更新委托信息成功，再更新样品信息
    if is_success:
        logger.info("开始同步样品信息")
        # 循环遍历数据库记录 一个委托对应多组样品信息
        logger.info(f"同步样品组数量：{len(db_data)}")
        at_least_one_success = False # 是否至少一个同步成功
        all_success = True # 是否全部同步成功
        order = 1
        for sample_db_data in db_data:
            logger.debug(f"开始同步第{order}个样品组信息，样品编号为：{sample_db_data['SampleNum']}")
            order += 1
            is_sample_sync_success = sync_sample_info(db_data=sample_db_data,yida_client=yida_client,xm_cloud_client=xm_cloud_client)
            # 只要有一个同步成功赋值至少一个成功为True
            if is_sample_sync_success:
                at_least_one_success = True
            else:
                all_success = False
        # 对返回值进行判断处理
        # 如果全部成功
        if all_success:
            return (True,True)
        if at_least_one_success:
            return (True,False)
        # 上述都不是则没有一个同步成功
        return (True,None)
    # 委托基本信息同步失败
    else: 
        entrustNum_error_log = f"entrustNum={entrustNum};dml_pattern={dml_pattern}"
        sample_error_log = None
        # 记录同步失败的样品信息 !!!样品组也要记录
        if db_data:
            sample_nums = []
            for sample_db_data in db_data:
                sample_nums.append(sample_db_data["SampleNum"])
            sample_nums_log = ','.join(sample_nums)
            sample_error_log = f"sampleNum={sample_nums_log}"
        else:
            logger.error(f"无法记录委托编号为{entrustNum}对应的样品组信息数据，查询数据库的数据无效！")
        # 如果有样品组数据就和委托日志合并
        if sample_error_log:
            entrustNum_error_log = entrustNum_error_log + ";" + sample_error_log
        SyncErrorHandler.write_sync_error_data(sync_name="委托",error_data=entrustNum_error_log,error_info="未知原因，同步失败！")                  
    # 委托信息没有同步成功，样品信息也无法进行同步
    return (False,None)

sync_current_record_executor_pool_max_workers = 8
sync_current_record_executor_pool = concurrent.futures.ThreadPoolExecutor(max_workers=sync_current_record_executor_pool_max_workers) # 同步工程线程池
sync_current_record_redis_lock_expire = 50 # redis锁的有效时间
sync_current_record_renew_sleep_count = 12 # 续签睡眠次数，次数耗尽进行续签
sync_current_record_get_redis_lock_period = sync_current_record_redis_lock_expire // 2 # 取过期时间的一半，获取redis锁的周期
sync_current_record_get_redis_lock_count = 12 # 获取redis锁最大次数，超过该次数则该次同步失败 !不能一直占有线程池的线程

def sync_current_record(company_name,dml_pattern,entrustNum):
    # 尝试获取redis分布式锁
    lock_name = f"sync-yida:entrustNum:{entrustNum}"
    sync_current_record_redis_lock = SyncHandleRedisLock(lock_name=lock_name,expire=sync_current_record_redis_lock_expire,renew_sleep_count=sync_current_record_renew_sleep_count)
    # 尝试周期性获取锁
    if sync_current_record_redis_lock.acquire_loop(get_period=sync_current_record_get_redis_lock_period,get_count=sync_current_record_renew_sleep_count):
        handle_sync_current_record(company_name,dml_pattern,entrustNum=entrustNum)
        # 释放锁后返回即可
        sync_current_record_redis_lock.release()
        return
    # 多次获取失败，结束任务，并记录同步失败
    # 删除本地委托编号缓存
    safe_sync_dict.delete(entrustNum)
    SyncErrorHandler.write_sync_error_data(sync_name="委托",error_data=f"entrustNum={entrustNum};dml_pattern={dml_pattern}",error_info="多次获取redis锁后失败")

@csrf_exempt
@require_http_methods(["GET"])
def sync_current_record_jy_sqlserver_to_yida(request,company_name):
    '''
    同步sqlserver数据库到宜搭数据中心
    '''
    req_data = request.GET
    # 获取操作方式 !不需要删除操作
    allowed_patterns = ["insert","update"]
    dml_pattern = req_data.get("dml_pattern")
    logger.info(f"The dml pattern is {dml_pattern}")
    # 校验操作方式是否有效
    if not dml_pattern:
        return ApiResponse.fail(message="无效的DML操作方式")
    dml_pattern = dml_pattern.lower()
    if dml_pattern not in allowed_patterns:
        return ApiResponse.fail(message="无效的DML操作方式")
    entrustNum = req_data.get("entrustNum","")
    # 如果没有数据或者为空
    if not entrustNum:
        return ApiResponse.fail(message="同步的数据为空")
    # 检查本地缓存有无相应的项目编号存在
    # 不存在则说明目前还没有正在同步的项目
    if safe_sync_dict.get(entrustNum) is None:
        # 加入该项目编号缓存
        set_success = safe_sync_dict.set(entrustNum,1)
        # 只有设置成功才能提交任务
        if set_success:
            # 提交任务 
            sync_current_record_executor_pool.submit(sync_current_record,company_name,dml_pattern,entrustNum)
    logger.info(f"存在相同的委托编号:{entrustNum}")
    return ApiResponse.success(message="同步委托请求发送成功")

@csrf_exempt
@require_http_methods(["POST"])
def sync_current_record_yida_to_jy_sqlserver(request,company_name):
    '''
    反向同步到数据库，需要整体事务的支持
    这里只需要考更新
    '''
    logger.info("委托反向同步宜搭数据中心到sqlserver数据库")
    req_data = request.POST
    # 获取操作方式
    allowed_patterns = ["update"]
    dml_pattern = req_data.get("dml_pattern")
    logger.info(f"The dml pattern is {dml_pattern}")
    # 校验操作方式是否有效
    if not dml_pattern:
        return ApiResponse.fail(message="无效的DML操作方式")
    dml_pattern = dml_pattern.lower()
    if dml_pattern not in allowed_patterns:
        return ApiResponse.fail(message="无效的DML操作方式")
    # 获取表记录实例id    
    form_instance_id = req_data.get("form_instance_id","")
    # 如果没有数据或者为空
    if not form_instance_id:
        return ApiResponse.success(message="同步的数据为空")
    
    # 创建宜搭客户端
    YidaClient = get_yida_client(company_name)
    yida_client:YidaClientBase = YidaClient()
    # 获取表单相关信息
    form_uuid = yida_client.current_record_form_uuid
    admin_user_id = yida_client.admin_user_id
    # 获取宜搭的表单某条记录
    # ?!删除的话表记录没了如何查询数据 @只管更新，不管删除
    table_items = yida_client.get_form_items_by_ids(form_uuid=form_uuid,form_inst_id=form_instance_id,userId=admin_user_id)
    if not table_items:
        return ApiResponse.fail(message="同步失败，未找到宜搭对应的表的实例id的记录信息")
    table_item = table_items[0]
    entrustNum = table_item.form_data[yida_client.current_record_primary_key_component_id]
    # 创建数据库工具
    db_name = f"{company_name}_db"
    if yida_client.is_custom:
        db_tool = CustomDBTool(db_name)
    else:
        db_tool = DBTool(db_name)
    form_data = table_item.form_data
    # 创建同步处理器链
    xm_cloud_client = get_xm_cloud_client(company_name=company_name)
    sync_handler_chain = SyncHandlerChain(yida_client=yida_client,xm_cloud_client=xm_cloud_client)
    updated_project_mapping = sync_handler_chain.inverse_sync_data(form_data,yida_client.current_record_sync_mapping)
    # 构造update sql语句进行更新
    update_sql = "update sysCurrentRecord set "
    for key,value in updated_project_mapping.items():
        key_str:str = key
        if key_str.lower() == "entrustnum":
            continue
        # 日期类型需要转化为 CONVERT(DATETIME, '2023-09-30', 120)
        if key.endswith('Date') or key.endswith("date"):
            handled_value = value
        else:
            handled_value = f"'{value}'" # 注意需要用''包裹
        update_sql += f"{key} = {handled_value},"
    # 去除最后的 , 号
    update_sql = update_sql[0:len(update_sql)-1]
    # 添加更新条件
    # !!!这里不采用防止sql注入了，否则返回的更新行数为0太奇怪了
    update_sql += f" where entrustNum = '{entrustNum}'"
    logger.info(f"Execute update sql {update_sql}")
    affected_row = db_tool.execute_update(update_sql,[])
    match affected_row:
        case -1:
            return ApiResponse.fail(message="更新失败，未知原因")
        case 0:
            return ApiResponse.success(message="更新成功，但数据库中不存在对应的记录")
        case _:
            return ApiResponse.success(message="更新成功")

'''状态模式类'''

class DMLPattern(ABC):
    '''抽象DML方式类'''
    def __init__(self,transformer:DMLPatternTransformer,dml_pattern:str) -> None:
        self.status = dml_pattern
        self.transformer = transformer
        # 反向注册进转换器的pattern中
        self.transformer.patterns[dml_pattern] = self
    
    @abstractmethod
    def has_data(self):
        pass

    @abstractmethod
    def no_data(self):
        pass

class InsertDMLPattern(DMLPattern):
    '''插入状态'''
    def __init__(self,transformer) -> None:
        super().__init__(transformer=transformer,dml_pattern='insert')

    def has_data(self):
        # @有数据的话应该变为update
        self.transformer.current_pattern = self.transformer.update_pattern

    def no_data(self):
        pass

class DeleteDMLPattern(DMLPattern):
    '''删除状态'''
    def __init__(self,transformer) -> None:
        super().__init__(transformer=transformer,dml_pattern='delete')

    def has_data(self):
        pass

    def no_data(self):
        # @没有的话定义为select，无效同步
        self.transformer.current_pattern = self.transformer.select_pattern     

class UpdateDMLPattern(DMLPattern):
    '''更新状态'''
    def __init__(self,transformer) -> None:
        super().__init__(transformer=transformer,dml_pattern='update')

    def has_data(self):
        pass

    def no_data(self):
        # @没有的话则变为插入操作
        self.transformer.current_pattern = self.transformer.insert_pattern        

class SelectDMLPattern(DMLPattern):
    '''查询状态'''
    def __init__(self,transformer:DMLPatternTransformer) -> None:
        super().__init__(transformer=transformer,dml_pattern='select')

    def has_data(self):
        pass

    def no_data(self):
        pass        

class DMLPatternTransformer:

    patterns = {}

    '''状态模式转换器'''
    def __init__(self,dml_pattern:str) -> None:
        # 注册状态
        self.insert_pattern = InsertDMLPattern(self)
        self.delete_pattern = DeleteDMLPattern(self)
        self.update_pattern = UpdateDMLPattern(self)
        self.select_pattern = SelectDMLPattern(self)
        # 设置当前状态
        self.__current_pattern = DMLPatternTransformer.patterns[dml_pattern]

    
    @property
    def current_pattern(self):
        return self.__current_pattern
    
    @current_pattern.setter
    def current_pattern(self,dml_pattern):
        self.__current_pattern = dml_pattern

    def transform(self,has_data):
        if has_data:
            self.current_pattern.has_data()
        else:
            self.current_pattern.no_data()
    
    def get_status(self):
        return self.current_pattern.status

'''同步处理器，用于处理各种字段'''

class SyncHandler(ABC):

    def __init__(self,yida_client:YidaClientBase,xm_cloud_client:XmCloudBaseClient):
        super().__init__()
        self.yida_client = yida_client
        self.xm_cloud_client = xm_cloud_client

    # 判断是否支持处理
    @abstractmethod
    def support_handle(self,key:str,value:str):
        pass

    # 正向同步 数据库 -> 宜搭
    '''

    return 处理后的值    
    '''
    @abstractmethod
    def sync_data(self,key:str,value:str,db_data:dict,mapped_data:dict):
        pass

    # 反向同步 宜搭 -> 数据库
    '''
    return 
    value 反向同步后处理的值，用于构造update sql语句
    None 表示无需处理
    '''
    @abstractmethod
    def inverse_sync_data(self,key:str,value:str,form_data:dict,mapped_data:dict): # #form_data类型待书写
        pass

'''默认值填充处理器，如source字段的处理'''
class DefaultSyncHandler(SyncHandler):

    def __init__(self, yida_client, xm_cloud_client):
        super().__init__(yida_client, xm_cloud_client)

    def support_handle(self,key,value):
        return key.startswith("default")

    def sync_data(self,key,value,db_data,mapped_data):
        default_config = self.yida_client.default_config
        default_value = default_config.get(key)
        # 如果为None，说明配置有问题
        if default_value == None:
            warning_msg = f"{key}对应的默认值为None，系统将视为None！请检查是否是有效的配置！"
            logger.warning(warning_msg)
        return default_value

    def inverse_sync_data(self,key,value,form_data,mapped_data):
        return None

'''引用值处理器，'&'的专门处理器'''
class ReferenceSyncHandler(SyncHandler):

    def __init__(self, yida_client, xm_cloud_client):
        super().__init__(yida_client, xm_cloud_client)

    def support_handle(self,key,value):
        return key.startswith("&")

    def sync_data(self,key,value,db_data,mapped_data):
        return mapped_data[key[1:]]

    def inverse_sync_data(self,key,value,form_data,mapped_data):
        return None
    
'''日期类型处理器'''
class DateSyncHandler(SyncHandler):

    def __init__(self, yida_client, xm_cloud_client):
        super().__init__(yida_client, xm_cloud_client)

    def support_handle(self,key,value):
        return value.startswith("dateField")

    def sync_data(self,key,value,db_data,mapped_data):
        db_value:datetime.datetime = db_data[key]
        # 不存在则返回None
        if not db_value:
            return None
        # 这里需要返回时间戳整型 单位是ms
        return int(db_value.timestamp() * 1000)

    def inverse_sync_data(self,key,value,form_data,mapped_data):
        timestamp_ms:int = form_data[value]
        # 没有则返回None
        if not timestamp_ms:
            return None
        form_value = datetime.datetime.fromtimestamp(timestamp_ms/1000,datetime.timezone.utc)
        form_value += datetime.timedelta(hours=8)
        time_str = datetime.datetime.strftime(form_value,'%Y-%m-%d %H:%M:%S')
        # 120即为对应的标准的格式 !注意分号
        handled_value = f"CONVERT(DATETIME, '{time_str}', 120)"
        return handled_value


'''联合表单处理器'''
class AssociationSyncHandler(SyncHandler):

    def __init__(self, yida_client, xm_cloud_client):
        super().__init__(yida_client, xm_cloud_client)

    def support_handle(self,key,value):
        return value.startswith("association")

    # 联合表单字段同步处理需要
    def sync_data(self,key,value,db_data,mapped_data):
        logger.info(f"处理同步联合字段{key + '-' +value}的相关信息")
        # 1.获取相关联的配置
        association_config:dict = self.yida_client.association_config
        value_association_config:dict = association_config[value]
        associated_form_uuid = value_association_config["formUuid"]
        search_config = value_association_config["search"]
        fill_config = value_association_config["fill"]
        sync_config = value_association_config["sync"]
        # 2.查找宜搭是否存在对应的记录，构造form_data用于填充
        copy_search_config = {**search_config}
        # 判断是否有指定的查询值的字段，用于同步判断
        search_db_field = copy_search_config.pop("db_field",None)
        if search_db_field == None:
            db_value = db_data[key]
        else:
            logger.debug(f"查询字段值{search_db_field}")
            db_value = db_data[search_db_field]
        logger.info(f"查询宜搭表条件主键值：{db_value}")
        assocaited_result = self.yida_client.get_form_data_by_search_field(form_uuid=associated_form_uuid,userId=self.yida_client.admin_user_id,
                                                    search_field=[
                                                        {
                                                            **copy_search_config,
                                                            "value":db_value
                                                        }
                                                    ]) 
        # case 2.1-存在则获取form_data
        if assocaited_result:
            logger.info("存在对应的联合记录")
            form_data = assocaited_result[0]
        # case 2.2-不存在则需要同步该表记录
        else:
            logger.info("不存在对应的联合记录")
            # 获取同步具体配置信息
            sync_func_name = sync_config["func"]
            company_name = sync_config["company_name"]
            import sys
            # 获取当前模块
            current_module = sys.modules[__name__]
            # 使用getattr获取函数
            # 不存在记录则从数据库中取出再插入到宜搭中去，再从宜搭中查询
            sync_func = getattr(current_module, sync_func_name)
            is_sync_success = sync_func(company_name,'insert',db_value)
            # 如果同步失败并且没有关联的数据库对象记录
            if not is_sync_success:
                logger.warning(f"关联对象记录同步宜搭失败")
                return []
            logger.info(f"关联对象记录同步宜搭成功")
            # 再查寻宜搭获取form_data
            form_data = self.yida_client.get_form_data_by_search_field(form_uuid=associated_form_uuid,userId=self.yida_client.admin_user_id,
                                                    search_field=[{
                                                        **copy_search_config,
                                                        "value":db_value
                                                    }])
            if not form_data:
                logger.warning(f"同步关联字段{key + '' + value}添加失败")
                return []
            form_data = form_data[0]
        # 3.重新赋值db_value，配成标准联合表单填充格式
        db_value = [{
            "appType": value_association_config["appType"],
            "formType": value_association_config["formType"],
            "formUuid": associated_form_uuid,
            "instanceId": form_data.form_instance_id,
            "title": form_data.form_data[fill_config["title_field_name"]]
        }]
        # 4.填充其他相关字段
        form_data = form_data.form_data
        form_fields:dict = fill_config.get("form_fields")
        if form_fields != None:
            for source_field,target_field in form_fields.items():
                mapped_data[target_field] = form_data[source_field]
        return db_value

    # 反向同步更新数据库某些字段 !!这里直接填充进mapped_data中，与key-value（辅助作用）无关，返回None即可
    def inverse_sync_data(self,key,value,form_data,mapped_data):
        # 1.获取相关联的配置
        association_config = self.yida_client.association_config
        value_association_config:dict = association_config[value]
        inverse_sync_config = value_association_config["inverse_sync"]
        if not inverse_sync_config:
            return None
        db_fields:dict = inverse_sync_config["db_fields"]
        # 2.填充db字段数据
        for db_field,form_field in db_fields.items():
            mapped_data[db_field] = form_data[form_field]
        return None

'''专门处理样品信息同步中检测参数的同步处理器'''
class SubItemListTableSyncHandler(SyncHandler):

    def __init__(self, yida_client, xm_cloud_client):
        super().__init__(yida_client, xm_cloud_client)

    # 判断是否支持处理
    def support_handle(self,key:str,value:str):
        return key == "subitemlist"
        
    
    @classmethod
    def generate_item_spec_query_sql(handler,sample_num,item_code,spec_field):
        # 如果没有规格，查询点数即可
        if not spec_field:
            return f"select pointcount from sysCurrentRecord where SampleNum = '{sample_num}'"
        # 有规格，查询点数和规格

# select a.SampleNum,a.pointcount,b.PDKSDJ from sysCurrentRecord a,HNKS_CUR b
# where a.sysprimarykey=b.sysprimarykey and a.SampleNum = 'LAJC-23HS0031501'
# union all
# select a.SampleNum,a.pointcount,b.PDKSDJ  from sysCurrentRecord a,HNKS_SAV b
# where a.sysprimarykey=b.sysprimarykey and a.SampleNum = 'LAJC-23HS0031501'        
        spec_pointcount_sql = f'''
                                select t1.pointcount,t2.{spec_field}
                                from sysCurrentRecord t1,{item_code}_CUR t2
                                where t1.sysprimarykey = t2.sysprimarykey
                                and t1.SampleNum = '{sample_num}'
                                union all
                                select t1.pointcount,t2.{spec_field}
                                from sysCurrentRecord t1,{item_code}_SAV t2
                                where t1.sysprimarykey = t2.sysprimarykey
                                and t1.SampleNum = '{sample_num}'
                              '''
        return spec_pointcount_sql

    # 正向同步 数据库 -> 宜搭
    def sync_data(self,key:str,value:str,db_data:dict,mapped_data:dict):
        # 1.从配置中获取中文名字，并查询对应的规格
        company_name = self.xm_cloud_client.corp_name
        if self.yida_client.is_custom:
            db_tool = CustomDBTool(f"{company_name}_db")
        else:
            db_tool = DBTool(f"{company_name}_db")
        project_code = mapped_data.get("ItemName")
        if not project_code:
            logger.warning(f"无效的检测项目代码：{project_code}")
            return None
        logger.info(f"同步的项目代码为：{project_code}")
        # 1.1 从跨表查询配置中获取对应的配置
        config_key = "item_spec_query_config"
        item_spec_query_config:dict = self.yida_client.join_table_query_config[config_key]
        # 如果没有配置，那么错误
        if not item_spec_query_config:
            error_msg = f"不存在联表查询配置中'{config_key}'对应的具体配置信息"
            logger.error(error_msg)
            return None
        project_code_config = item_spec_query_config.get(project_code)
        # 对应的检测项目配置项目应该都在配置中，不存在找不到的情况，否则报错
        if project_code_config == None:
            error_msg = f"不存在项目代码{project_code}对应的跨表查询配置！请添加后再同步！"
            logger.error(error_msg)
            return None
        # 1.2 获取配置项目的信息
        item_ch_name = project_code_config["ItemChName"]
        logger.info(f"中文名字为{item_ch_name}")
        # 1.3 重新赋值为中文名字
        mapped_data["ItemName"] = item_ch_name
        spec_field = project_code_config["specField"]
        logger.info(f"数据库表中规则字段为{spec_field if spec_field else '空'}")
        # 获取样品编号
        sample_num = db_data["SampleNum"]
        logger.info(f"查询的样品编号为{sample_num}")
        # 1.4 生成对应的查询sql，@@@这里的db_data是含有特定样品编号的信息，因此返回的记录有且只有一行
        query_sql = self.generate_item_spec_query_sql(sample_num=sample_num,item_code=project_code,spec_field=spec_field)
        # 1.5 开始查询，@返回的是样品组的规格，以及点数
        sample_spec_pointcount_dict = db_tool.execute_query(query=query_sql,params=[],return_dict=True)
        # 如果没有对应的记录
        if not sample_spec_pointcount_dict:
            waring_msg = f"样品编号{sample_num}不存在对应的点数-规格记录"
            logger.warning(waring_msg)
            return None
        logger.debug(f"点数-规格数据库记录：{sample_spec_pointcount_dict}")
        # 取第一个值即可
        sample_spec_pointcount_dict = sample_spec_pointcount_dict[0]
        # 获取点数值
        pointcount = sample_spec_pointcount_dict["pointcount"]
        # 必须是数字型字符串，不是则为1，发出警告
        if not common_utils.is_integer_or_strint(pointcount):
            waring_msg = f"点数值'{pointcount}'不是有效的数字！默认设置为1！"
            logger.warning(waring_msg)
            pointcount = 1
        else:
            # 没有的话置为1
            pointcount = int(pointcount) if pointcount else 1
        # 获取规格值，如果有则获取
        if spec_field:
            spec_value = sample_spec_pointcount_dict[spec_field]
            # 如果找不到或者为空
            if not spec_value:
                spec_value = None
                logger.warning(f"样品编号{sample_num}有规格字段{spec_field}但是查询不到对应的规格值！置为空！")
        # 没有规格字段置为空即可
        else:
            spec_value = None
        # 获取关联的参数名字
        associated_parameter_name = project_code_config.get("associatedParameterName")
        # 校验，如果有规格那么一定有关联参数名字，否则报错
        # 存储规格值到宜搭字段中
        spec_storage_field = item_spec_query_config.get("specStorageField")
        if not spec_storage_field:
            logger.error(f"没有配置规格存储字段！规格值将不会同步到宜搭表中！")
        if spec_value:
            # 如果是有效的宜搭唯一标识
            if spec_storage_field:
                mapped_data[spec_storage_field] = spec_value
            if not associated_parameter_name:
                error_msg = f"错误：'{project_code}-{item_ch_name}'检测项目有规格值但没有关联的参数名称"
                logger.error(error_msg)
                return None
        # 2.对检测参数进行分割处理，分割符注意是中文顿号
        sub_items = db_data[key].split('、')
        # 3.从云平台中获取对应的检测参数的计价信息
        logger.debug(f"开始从云平台中获取对应的检测参数的计价信息")
        # 构造头部验证信息
        auth_header_config = {'Authorization':self.xm_cloud_client.xm_token}
        # 构造查询数据
        query_data = {}
        query_data["project_name"] = item_ch_name
        query_data["project_spec"] = spec_value
        query_data["associated_parameter_name"] = associated_parameter_name
        query_data["parameter_names"] = sub_items
        response:dict = HTTPUtils.post("/xu-cloud/detection-parameter/get-strategy-cost-info-by-names/",data=query_data,headers_config=auth_header_config)
        # 如果是无效response
        if not response:
            logger.error(f"云平台内部系统异常，非200请求成功响应码！")
            return None
        # 如果是错误response
        if response["code"] == 400:
            error_msg = response.get("message")
            logger.error(f"云平台错误信息：{error_msg}")
            return None
        # 如果是有效response
        success_msg = response.get("message")
        logger.info(f"云平台成功返回消息：{success_msg}")
        # 获取计价数据
        strategy_cost_info_data_list:list[dict] = response.get("data")
        logger.info(f"云平台返回检测参数计价数据：{strategy_cost_info_data_list}")
        if not strategy_cost_info_data_list:
            logger.warning(f"云平台检测参数计价信息为空！")
            return None
        # 转换计价为宜搭数据然后返回
        logger.info(f"开始转化为宜搭所需的计价数据")
        item_table_config = self.yida_client.table_config[value]
        cost_sum = 0
        return_yida_cost_data_list = [] # 返回的转换后的计价信息列表
        # 遍历计价信息
        for strategy_cost_info_item in strategy_cost_info_data_list:        
            transformed_yida_map = {} # 计价信息转换宜搭映射
            # 遍历需要转换的配置项目
            for table_key,table_value in item_table_config["table_fields"].items():
                # 如果是点数那么就赋值上述的查询出来的点数即可
                if table_key == "pointcount":
                    transformed_yida_map[table_value] = pointcount
                    continue
                transformed_yida_map[table_value] = strategy_cost_info_item[table_key]
                # 如果是单价还需要计算总和
                if table_key == "single_cost":
                    # 这里要乘以点数
                    cost_sum += strategy_cost_info_item["single_cost"] * pointcount
            # 加入到新的转换后list中
            return_yida_cost_data_list.append(transformed_yida_map)
        # 注入总价字段
        cost_yida_field = item_table_config["cost"]
        mapped_data[cost_yida_field] = cost_sum
        logger.info(f"计价后总价格为{cost_sum}")
        return return_yida_cost_data_list

    # 反向同步 宜搭 -> 数据库
    def inverse_sync_data(self,key:str,value:str,form_data:dict,mapped_data:dict,association_config:dict): # #form_data类型待书写
        return None   

class SyncHandlerChain:

    def __init__(self,yida_client:YidaClientBase,xm_cloud_client:XmCloudBaseClient):
        self.yida_client = yida_client
        self.xm_cloud_client = xm_cloud_client
        # 注册处理器
        default_handler = DefaultSyncHandler(yida_client=yida_client,xm_cloud_client=xm_cloud_client)
        reference_handler = ReferenceSyncHandler(yida_client=yida_client,xm_cloud_client=xm_cloud_client)
        date_handler = DateSyncHandler(yida_client=yida_client,xm_cloud_client=xm_cloud_client)
        association_handler = AssociationSyncHandler(yida_client=yida_client,xm_cloud_client=xm_cloud_client)
        # 处理器集合 要求最先适合的优先排在前面
        # 定义默认处理器
        self.default_handlers:list[SyncHandler] = [default_handler,reference_handler,date_handler,association_handler]
        # 定义自定义处理器
        self.custom_handlers:list[SyncHandler] = []
        self.custom_handlers_name_class_dict = {} # 自定义处理器名字到

    '''
    return 
    dict:宜搭格式field->value
    '''
    def sync_data(self,db_data:dict,field_mapping_config:dict) -> dict|None:
        # 合并处理器
        handlers = [*self.default_handlers,*self.custom_handlers]
        # 处理数据
        mapped_sync_data = {}
        # 遍历映射配置处理
        for key,value in field_mapping_config.items():
            is_handled = False
            # 遍历处理器处理
            for sync_handler in handlers:
                # 支持的话则进行处理 直接退出即可
                if sync_handler.support_handle(key,value):
                    is_handled = True
                    mapped_sync_data[key] = sync_handler.sync_data(key,value,db_data,mapped_sync_data)
                    break
            # 不支持则保留原始值
            if not is_handled:
                mapped_sync_data[key] = db_data[key]
        # 处理完毕，转化为宜搭表字段映射处理器后的值
        for key,value in field_mapping_config.items():
            handled_value = mapped_sync_data.pop(key)
            mapped_sync_data[value] = handled_value
        return mapped_sync_data

    '''
    return 
    dict:数据库列名->组成update sql句子的value
    '''
    def inverse_sync_data(self,form_data:dict,field_mapping_config:dict) -> dict|None:
        # 合并处理器
        handlers = [*self.default_handlers,*self.custom_handlers]
        # 处理数据
        mapped_sync_data = {}
        # 遍历映射配置处理
        for key,value in field_mapping_config.items():
            is_handled = False
            # 遍历处理器处理
            for sync_handler in handlers:
                # 支持的话则进行处理
                if sync_handler.support_handle(key,value):
                    is_handled = True
                    handled_value = sync_handler.inverse_sync_data(key,value,form_data,mapped_sync_data)
                    # 如何反向同步返回None，表明无需加入mapped_sync_data中
                    if handled_value is None:
                        break
                    mapped_sync_data[key] = handled_value
                    break
            # 不支持则保留原始值
            if not is_handled:
                mapped_sync_data[key] = form_data[value]
        return mapped_sync_data        

    '''注册自定义处理器'''
    def register_custom_handler(self,handler_class:SyncHandler,handler_name:str=None):
        # 如果不是对应的子类，那么抛出异常
        if not issubclass(handler_class,SyncHandler):
            raise ValueError("注册的处理器类并不是SyncHandler的子类")
        # 如果已经注册了则返回None并提示
        handler_name = handler_name if handler_name else handler_class.__class__.__name__
        if self.custom_handlers_name_class_dict.get(handler_name) != None:
            logger.warning("你已经注册过相同名字的处理器了，请勿重复注册！")
            return None
        # 没有注册过则注册
        self.custom_handlers_name_class_dict[handler_name] = handler_class
        # 新建一个处理器
        handler_instance = handler_class(self.yida_client,self.xm_cloud_client)
        self.custom_handlers.append(handler_instance)
        return handler_instance

class SyncErrorHandler:

    sync_logger = logging.getLogger("sync_logger")

    class SyncErrorDataStatus(Enum):

        def __init__(self,status):
            self.status = status
        
        def __str__(self):
            return str(self.status)

        ERROR = (-1) # 错误待后续处理同步
        ERROR_HANDLED = (0) # 错误已经处理
        ERROR_EXCEPTION = (1) # 错误异常状态，多次处理无法成功

    @classmethod
    def write_sync_error_data(handler,sync_name,error_data,error_info):
        logger.error(f"同步{sync_name}失败！具体原因：{error_info}！")
        # 默认是错误待处理状态
        handler.sync_logger.error(error_data+";"+f"sync_status={SyncErrorHandler.SyncErrorDataStatus.ERROR};error_reason={error_info}")
